const ejs = require('ejs')
const path = require('path')
const async = require('neo-async')
const { Tapable, SyncHook } = require('tapable')
const NormalModuleFactory = require('./NormalModuleFactory.js')
const Parser = require('./Parser.js')
const Chunk = require('./Chunk.js')
const normalModuleFactory = new NormalModuleFactory()
const parser = new Parser()
class Compilation extends Tapable {
  constructor(compiler) {
    super()
    this.compiler = compiler
    this.context = compiler.context
    this.options = compiler.options
    this.inputFileSystem = compiler.inputFileSystem
    this.outputFileSystem = compiler.outputFileSystem
    this.entries = [] // 存放入口模块 
    this.modules = [] // 存放所以模块数据
    this.chunks = [] // 存放chunks
    this.assets = []
    this.files = []
    this.hooks = {
      succeedModule: new SyncHook(['module']),
      seal: new SyncHook(),
      beforeChunks: new SyncHook(),
      afterChunks: new SyncHook()
    }
  }

  addEntry(context, entry, name, callback) {
    this._addModuleChain(context, entry, name, (err, module) => {
      callback(err, module)
    })
  }

  _addModuleChain(context, entry, name, callback) {
    this.createModule(
      {
        name: name,
        context: context,
        rawRequest: entry,
        resource: path.posix.join(context, entry),// 返回entry入口绝对路径
        moduleId: './' + path.posix.relative(context, path.posix.join(context, entry)),
        parser: parser
      },
      (module) => { this.entries.push(module) },
      callback
    )
  }

  /**
   * 定义创建模块
   * @param {*} data 
   * @param {*} doAddEntry 
   * @param {*} callback 
   */
  createModule(data, doAddEntry, callback) {
    let module = normalModuleFactory.create(data)

    doAddEntry && doAddEntry(module)

    const afterBuild = (err, module) => {
      if (module.dependencies.length > 0) {
        // module有需要依赖加载的模块
        this.processDependencies(module, (err) => {
          callback(err, module)
        })
      } else {
        callback(err, module)
      }
    }

    this.buildModule(module, afterBuild)

    this.modules.push(module)
  }

  buildModule(module, callback) {
    module.build(this, (err) => {
      this.hooks.succeedModule.call(module)
      callback(err, module)
    })
  }

  processDependencies(module, callback) {
    // 1 被依赖模块进行递归加载

    async.forEach(module.dependencies, (dependency, done) => {
      this.createModule({
        name: dependency.name,
        context: dependency.context,
        rawRequest: dependency.rawRequest,
        resource: dependency.resource,// 返回entry入口绝对路径
        moduleId: dependency.moduleId,
        parser: parser
      }, null, done)
    }, callback)
  }

  seal(callback) {
    this.hooks.seal.call()
    this.hooks.beforeChunks.call()
    for (const entryModule of this.entries) {
      const chunk = new Chunk(entryModule)

      this.chunks.push(chunk)

      chunk.modules = this.modules.filter(module => module.name === chunk.name) // 过滤已存在的module
    }

    this.hooks.afterChunks.call(this.chunks)

    this.createChunkAssets()

    callback()
  }

  createChunkAssets() {
    for (let i = 0; i < this.chunks.length; i++) {
      const chunk = this.chunks[i]
      const fileName = chunk.name + '.js'
      chunk.files.push(fileName)

      // 获取模版路径
      let tempPath = path.posix.join(__dirname, 'temp/main.ejs');
      // 读取模版内容
      let tempCode = this.inputFileSystem.readFileSync(tempPath, 'utf8')
      // 获取渲染函数
      let tempRender = ejs.compile(tempCode)
      // 渲染数据
      let source = tempRender({
        entryModuleId: chunk.entryModule.moduleId,
        modules: chunk.modules
      })
      this.emitAssets(fileName, source)
    }

  }

  emitAssets(fileName, source) {
    this.assets[fileName] = source
    this.files.push(fileName)
  }
}

module.exports = Compilation